package Risk.Moteur;

import Risk.IHM.ConnexionServeurIHM;
import Risk.IHM.MenuConnexion;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 *  Pays
 *  @version    1.0, 07/12/26
 *   @author     Alexandre LIGUORI
 */
public class Pays implements Serializable {

    /**
     * erreur
     */
    public static int	ERREUR = -1;

    /**
     * le pays attaquant n a pas conquit le pays defenseur
     */
    public static int	NULL = 0;

    /**
     * le pays attaquant a conquit le pays defenseur
     */
    public static int	VAINQUEUR = 1;

    // Sebastien : Ajout d'un champ pour securiser la serialisation
    private static final long	serialVersionUID = 13L;
    private Joueur		joueur;
    private int			nbArmee;
    private String		nom;
    private Plateau		plateau;
    private String		renfort;
    private ArrayList<Pays>	voisins;

    /**
     * Constructeur de pays
     * @param nom nom du pays
     * @param renfort valeur de renfort (pour les cartes)
     * @param plateau le plateau
     */
    public Pays(String nom, String renfort, Plateau plateau) {
	this.joueur  = null;
	this.renfort = renfort;
	this.nom     = nom;
	this.voisins = new ArrayList<Pays>();
	this.nbArmee = 0;
	this.plateau = plateau;
    }

    /**
     * ajoute le nombre de renfort au armee deja presente dans le pays
     * @param renfort le nombre d armee a ajouter
     */
    public void ajouteArmee(int renfort) {
	this.nbArmee += renfort;
    }

    /**
     * ajoute un pays voisin
     * @param val pays voisin
     * @return tru si reussi, false sinon
     */
    public boolean ajouteVoisin(Pays val) {
	return this.voisins.add(val);
    }

    /**
     * le pays courant attaque le pays defenseur,
     * on doit passer en parametre les listes des jet de des
     * de l attaquant et du defenseur
     * @param defenseur la pays defenseur
     * @param jetAttaque liste des jet de des de l attaquant
     * @param jetDefense liste des jet de des du defenseur
     * @return VAINQUEUR si l attaquant prend possesion du pays defenseur, NULL sinon
     * (ERREUR si probleme)
     */
    public int attaque(Pays defenseur, ArrayList<Integer> jetAttaque, ArrayList<Integer> jetDefense) {
	int	resultat;

	// tests si tout ok
	if (!testFrontiere(defenseur)) {
	    System.out.println("Erreur methode attaque");

	    return ERREUR;
	}

	// bataille
	resultat = simuleBataille(defenseur, jetAttaque, jetDefense);

	// resolution bataille
	if (resultat >= VAINQUEUR) {
	    if (!prendPossesion(defenseur, resultat)) {    // on met automatiquement le nombre de soldats ayant attaqué
		System.out.println("Erreur 2 methode attaque");

		return ERREUR;
	    }

	    if (ConnexionServeurIHM.CONNECTE) {            // MAJ des info dans le textArea de l ihm
		plateau.getRisk().getPihm().getJtaConsole().append(
		    plateau.getRisk().getTc().envoieInformationsPays(defenseur) + "\n");
		plateau.getRisk().getPihm().getJtaConsole().append(
		    plateau.getRisk().getTc().envoieInformationsPays(this) + "\n");
	    }

	    System.out.println(getJoueur().getNom() + " PREND POSSESSION DE " + defenseur.getNom());

	    return VAINQUEUR;    // proposer au joueur de deplacer + de soldats
	} else {
	    return NULL;
	}
    }

    /**
     * test si le joueur val controle le pays
     * @param val le joueur
     * @return true s il controle le pays, false sinon
     */
    public boolean controlePays(Joueur val) {
	if (joueur.getNom().equals(val.getNom())) {
	    return true;
	} else {
	    return false;
	}
    }

    /**
     * deplace les armee nbArmee du pays courant vers le pays arrive
     * @param arrive le pays d arrive
     * @param nbArmee le nombre d armee a deplacer
     * @return true si ok, false sinon
     */
    public boolean deplaceArmeeVers(Pays arrive, int nbArmee) {
	if (!testDeplacement(arrive) ||!verifieNbSoldatDeplacement(nbArmee, this)) {
	    System.out.println("Erreur methode deplaceArmeeVers");

	    return false;
	}

	// effectue le deplacement des armees
	this.nbArmee -= nbArmee;
	arrive.ajouteArmee(nbArmee);

	return true;
    }

    /**
     * initialise la liste des pays voisin via un fichier
     * @param fic le chemin du fichier
     */
    public void initialiseVoisins(String fic) {
	String		pays_voisin = "";
	FileReader	fl          = null;
	BufferedReader	bf          = null;
	String		ligne       = null;
	StringTokenizer	stok;

	try {
	    File	file = new File(fic);

	    fl    = new FileReader(file);
	    bf    = new BufferedReader(fl);
	    ligne = bf.readLine();

	    while (ligne != null) {
		stok = new StringTokenizer(ligne, " ");

		if (stok.nextToken().equals(this.getNom())) {
		    pays_voisin = stok.nextToken();

		    if (recherchePays(pays_voisin) == null) {
			System.out.println("ERREUR méthode initialiseVoisins || introuvable => " + pays_voisin);

			return;
		    }

		    ajouteVoisin(recherchePays(pays_voisin));
		}

		ligne = bf.readLine();
	    }
	} catch (IOException ex) {
	    ex.printStackTrace();
	} finally {
	    try {
		bf.close();
		fl.close();
	    } catch (IOException ex) {
		ex.printStackTrace();
	    }
	}
    }

    /**
     * simule le jet du nombre de des nbDe du pays attaquant p
     * @param nbDe le nombre de des
     * @param p le pays attaquant
     * @return la liste des jets de des
     */
    public static ArrayList<Integer> jetAttaquant(int nbDe, Pays p) {
	ArrayList<Integer>	jet = new ArrayList<Integer>(nbDe);
        sleep(1);
	Random			de  = new Random(System.currentTimeMillis());

	if (!verifieNbSoldatAttaque(nbDe, p)) {
	    System.out.println("Trop attaquants");

	    return null;
	}

	for (int i = 0; i < nbDe; i++) {
	    sleep(1);
	    jet.add(de.nextInt(6) + 1);
	}

	return jet;
    }

    /**
     * simule le jet du nombre de des nbDe du pays defenseur p
     * @param nbDe le nombre de des
     * @param p le pays defenseur
     * @return la liste des jets de des
     */
    public static ArrayList<Integer> jetDefenseur(int nbDe, Pays p) {
	ArrayList<Integer>	jet = new ArrayList<Integer>(nbDe);
        sleep(1);
	Random			de  = new Random(System.currentTimeMillis());

	if (!verifieNbSoldatDefense(nbDe, p)) {
	    System.out.println("Trop defenseurs");

	    return null;
	}

	for (int i = 0; i < nbDe; i++) {
	    sleep(1);
	    jet.add(de.nextInt(6) + 1);
	}

	return jet;
    }

    /**
     * le joueur du pays courant prend possesion du pays nouvauPays,
     * et y deplace le nombre d armee nbArmee
     * @param nouveauPays le nouveau pays controle
     * @param nbArmee le nombre d armee deplace
     * @return true si ok, false sinon
     */
    private boolean prendPossesion(Pays nouveauPays, int nbArmee) {
	nouveauPays.setJoueur(this.getJoueur());

	if (!deplaceArmeeVers(nouveauPays, nbArmee)) {
	    System.out.println("Erreur methode prendPossesion");

	    return false;
	}

	return true;
    }

    /**
     * recherche le pays dont le nom est passe en parametre
     * @param nom le nom du pays recherche
     * @return l objet pays recherche, si non trouve null
     */
    private Pays recherchePays(String nom) {
	for (Continent conti : plateau.getContinents()) {
	    for (Pays pays : conti.getPaysContinent()) {
		if (pays.getNom().equals(nom)) {
		    return pays;
		}
	    }
	}

	System.out.println("Erreur methode recherchePays");

	return null;
    }

    /**
     * retire le nombre de mort au armee deja presente dans le pays
     * @param mort le nombre d armee a retirer
     */
    public void retireArmee(int mort) {
	this.nbArmee -= mort;
    }

    /**
     * simule la bataille avec le pays defenseur, en comparant les jets dattaqe et les jets de defense
     * @param defenseur la pays defenseur
     * @param jetAttaque liste des jet de des de l attaquant
     * @param jetDefense liste des jet de des du defenseur
     * @return le nombre de soldats attaquants restant si l attaquant prend possesion su pays
     * NULL si l attaquant n a pas conquit le pays defenseur
     * (ERREUR si probleme)
     */
    private int simuleBataille(Pays defenseur, ArrayList<Integer> jetAttaque, ArrayList<Integer> jetDefense) {
	if ((jetAttaque == null) || (jetDefense == null)) {
	    System.out.println("Certains jets de des sont null");

	    return ERREUR;
	}

	ArrayList<Integer>	tabAttaque      = jetAttaque;
	ArrayList<Integer>	tabDefense      = jetDefense;
	int			compteurAttaque = jetAttaque.size();

	// classement des meilleurs des
	Collections.sort(tabAttaque);
	Collections.sort(tabDefense);

	// resolution
	for (int i = 0; (i < jetDefense.size()) && (i < jetAttaque.size()); i++) {
	    System.out.println("de attaque=" + tabAttaque.get(tabAttaque.size() - 1 - i) + " <-> " + "de defense="
			       + tabDefense.get(tabDefense.size() - 1 - i));

	    if (tabAttaque.get(tabAttaque.size() - 1 - i) <= tabDefense.get(tabDefense.size() - 1 - i)) {
		System.out.println("DEFENSE GAGNE !");
		retireArmee(1);

		if (ConnexionServeurIHM.CONNECTE) {
		    plateau.getRisk().getPihm().getJtaConsole().append(
			plateau.getRisk().getTc().envoieInformationsPays(this) + "\n");    // attaquant perd 1 armee
		}

		compteurAttaque--;
	    } else {
		System.out.println("ATTAQUE GAGNE !");
		defenseur.retireArmee(1);

		if (ConnexionServeurIHM.CONNECTE) {
		    plateau.getRisk().getPihm().getJtaConsole().append(
			plateau.getRisk().getTc().envoieInformationsPays(defenseur) + "\n");
		}
	    }
	}

	// test si attaque a reussi
	if (defenseur.getNbArmee() > 0) {

	    // le defenseur a encore des armees sur le pays
	    return NULL;
	} else {

	    // attaque reussi, donc on renvoie le nb de soldats restants a deplacer
	    return compteurAttaque;
	}
    }

    /**
     * sleep (pour faire varier le random)
     * @param n temps su sleep (en ms)
     */
    private static void sleep(long n) {
	try {
	    Thread.sleep(n);
	} catch (InterruptedException ex) {
	    ex.printStackTrace();
	}
    }

    /**
     * test si un deplacement d armee du pays courant au pays arrive est autorise
     * @param arrive le pays d arrive
     * @return true si ok, false sinon
     */
    public boolean testDeplacement(Pays arrive) {
	ArrayList<Pays>	visite = new ArrayList<Pays>();    // tableau des pays visite
	Vector<Pays>	file   = new Vector<Pays>();
	Pays		tmp;

	file.add(this);

	while (!file.isEmpty()) {
	    tmp = file.lastElement();
	    file.remove(file.lastElement());

	    if (!visite.contains(tmp)) {
		visite.add(0, tmp);

		// traitement
		if (tmp.getJoueur() != null) {
		    if (tmp.getJoueur().getNom().equals(this.getJoueur().getNom())) {
			if (tmp.getNom().equals(arrive.getNom())) {

			    // Deplacement autorise
			    return true;
			}
		    }
		}

		for (Pays elem : tmp.getVoisins()) {
		    if (tmp.getJoueur() != null) {
			if (tmp.getJoueur().getNom().equals(this.getJoueur().getNom())) {
			    if (!visite.contains(elem)) {
				file.add(0, elem);
			    }
			}
		    }
		}
	    }
	}

	System.out.println("Impossible de faire la liaison");

	return false;
    }

    /**
     * test si le pays est bien frontalier avec le pays voisin
     * @param voisin le pays voisin
     * @return true si les pays sont frontalier, false sinon
     */
    public boolean testFrontiere(Pays voisin) {
	for (Pays pays : voisins) {
	    if (pays.getNom().equals(voisin.getNom())) {
		return true;
	    }
	}

	System.out.println("Erreur sur testFrontiere");

	return false;
    }

    /**
     * to string
     * @return string
     */
    @Override
    public String toString() {
	return this.nom;
    }

    /**
     * verfie le nombre de soldats nbSoldat su pays attaquant p
     * @param nbSoldat le nombre de soldats
     * @param p le pays attaquant
     * @return true si ok, false sinon
     */
    private static boolean verifieNbSoldatAttaque(int nbSoldat, Pays p) {
	if (verifieNbSoldatDeplacement(nbSoldat, p) && (nbSoldat <= 3)) {
	    return true;
	}

	System.out.println("Erreur sur verifieNbSoldatAttaque");

	return false;
    }

    /**
     * verfie le nombre de soldats nbSoldat su pays defenseur p
     * @param nbSoldat le nombre de soldats
     * @param p le pays defenseur
     * @return true si ok, false sinon
     */
    private static boolean verifieNbSoldatDefense(int nbSoldat, Pays p) {
	if ((nbSoldat <= 2) && (nbSoldat <= p.getNbArmee())) {
	    return true;
	}

	System.out.println("Erreur sur verifieNbSoldatDefense");

	return false;
    }

    /**
     * verifie si le nombre de soldat a deplacer d un pays p
     * @param nbSoldat le nombre de soldat a deplacer
     * @param p le pays
     * @return true si ok, false sinon
     */
    private static boolean verifieNbSoldatDeplacement(int nbSoldat, Pays p) {
	if (p.getNbArmee() > nbSoldat) {
	    return true;
	}

	System.out.println("Erreur sur verifieNbSoldatDeplacement");

	return false;
    }

    /**
     * renvoie le joueur qui controle le pays
     * @return le joueur qui controle le pays
     */
    public Joueur getJoueur() {
	return joueur;
    }

    /**
     * renvoie le nombre d armee occupant le pays
     * @return nombre d armee occupant le pays
     */
    public int getNbArmee() {
	return nbArmee;
    }

    /**
     * renvoie le nom du pays
     * @return nom du pays
     */
    public String getNom() {
	return nom;
    }

    /**
     * renvoie la valeur de renfort lie au pays (pour les cartes)
     * @return valeur de renfort (fantassin, cavalier, canon)
     */
    public String getRenfort() {
	return renfort;
    }

    /**
     * renvoie la liste des pays voisins
     * @return liste des pays voisins
     */
    public ArrayList<Pays> getVoisins() {
	return voisins;
    }

    /**
     * changer le joueur qui controle le pays
     * @param val le nouveau joueur qui controle le pays
     */
    public void setJoueur(Joueur val) {
	this.joueur = val;
    }

    /**
     * changer le nombre d armee dans le pays
     * @param val nouveau nombre d armee dans le pays
     */
    public void setNbArmee(int val) {
	this.nbArmee = val;
    }
}
